package com.by.gateway.service.impl;

import com.by.gateway.service.GatewayForwardService;
import com.by.gateway.service.EndpointInfo;
import com.by.gateway.service.EndpointService;
import com.by.log.Logger;
import com.by.log.LoggerFactory;
import com.by.rpc.dto.RpcRequest;
import com.by.rpc.dto.RpcResponse;
import com.by.rpc.dto.ServiceInstance;
import com.by.rpc.enums.ParameterPosition;
import com.by.rpc.netty.RpcClient;
import com.by.rpc.pool.RpcConnectionPool;
import com.by.rpc.util.ServiceParameter;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CompletionException;

@Slf4j
@Service
public class GatewayForwardServiceImpl implements GatewayForwardService {

    @Autowired
    private EndpointService endpointService;

    private final ObjectMapper mapper = new ObjectMapper();
    private final Map<String, RpcConnectionPool> connectionPools = new ConcurrentHashMap<>();
    private static final Logger logger = LoggerFactory.getLogger(GatewayForwardServiceImpl.class);

    // 你的 GatewayForwardServiceImpl
    @Value("${rpc.connection-pool.max-connections:200}") // 从配置文件读取，默认值200
    private int maxConnections;

    @Value("${rpc.connection-pool.min-connections:50}")
    private int minConnections;

    @Value("${rpc.connection-pool.max-idle-time-ms:60000}")
    private long maxIdleTimeMs;

    @Override
    public CompletableFuture<Object> forward(Map<String, Object> requestData) {
        try {
            String path = (String) requestData.get("$path");
            String httpMethod = (String) requestData.get("$method");

            log.info("Forwarding request: {} {}", httpMethod, path);

            EndpointInfo endpointInfo = endpointService.getTargetEndpointInfo(path, httpMethod);
            if (endpointInfo == null) {
                return CompletableFuture.failedFuture(new RuntimeException("No matching endpoint found for path: " + path + " and method: " + httpMethod));
            }

            List<ServiceParameter> parameters = buildServiceParameters(endpointInfo, requestData);

            RpcRequest rpcRequest = new RpcRequest();
            rpcRequest.setRequestId(java.util.UUID.randomUUID().toString());
            rpcRequest.setServiceName(endpointInfo.getServiceName());
            rpcRequest.setVersion(endpointInfo.getVersion());
            rpcRequest.setGroup(endpointInfo.getGroup());
            rpcRequest.setMethodName(endpointInfo.getRpcMethodName());
            rpcRequest.setParameters(parameters);

            log.debug("Constructed RPC request: {}", rpcRequest);

            ServiceInstance targetInstance = endpointInfo.getServiceInstance();
            if (targetInstance == null) {
                return CompletableFuture.failedFuture(new RuntimeException("No available service instance found for: " + endpointInfo.getServiceName()));
            }

            String poolKey = targetInstance.getHost() + ":" + targetInstance.getPort();
            RpcConnectionPool connectionPool = connectionPools.computeIfAbsent(poolKey,
                    k -> new RpcConnectionPool(targetInstance.getHost(), targetInstance.getPort(), maxConnections, minConnections, maxIdleTimeMs));

            // 异步获取连接，并使用thenCompose将后续操作链接起来
            return connectionPool.getConnectionAsync().thenCompose(rpcClient -> {
                // 在获取到 rpcClient 后，再发起异步 RPC 请求
                return rpcClient.sendRequest(rpcRequest).thenApply(response -> {
                    log.debug("Received RPC response: {}", response);
                    connectionPool.returnConnection(rpcClient);
                    endpointService.recordServiceCallResult(endpointInfo.getServiceName(), true);
                    if (response.getStatus() == RpcResponse.ResponseStatus.SUCCESS) {
                        return response.getData();
                    } else {
                        throw new CompletionException("RPC call failed: " + response.getErrorMsg(), null);
                    }
                }).exceptionally(e -> {
                    log.error("RPC call failed or timed out", e);
                    connectionPool.returnConnection(rpcClient);
                    endpointService.recordServiceCallResult(endpointInfo.getServiceName(), false);
                    throw new CompletionException("RPC call failed: " + e.getMessage(), e);
                });
            });

        } catch (Exception e) {
            log.error("Failed to forward request", e);
            return CompletableFuture.failedFuture(new RuntimeException("Failed to forward request: " + e.getMessage(), e));
        }
    }

    @PreDestroy
    public void destroy() {
        for (Map.Entry<String, RpcConnectionPool> entry : connectionPools.entrySet()) {
            try {
                entry.getValue().close();
                log.info("Closed RPC connection pool: {}", entry.getKey());
            } catch (Exception e) {
                log.warn("Failed to close RPC connection pool: {}", entry.getKey(), e);
            }
        }
        connectionPools.clear();
    }

    private List<ServiceParameter> buildServiceParameters(EndpointInfo endpointInfo, Map<String, Object> requestData) {
        List<ServiceParameter> serviceParameters = new ArrayList<>();
        List<ServiceParameter> definedParameters = endpointInfo.getServiceParameters();
        if (definedParameters != null && !definedParameters.isEmpty()) {
            for (ServiceParameter param : definedParameters) {
                ServiceParameter serviceParam = new ServiceParameter();
                serviceParam.setOrder(param.getOrder());
                serviceParam.setName(param.getName());
                serviceParam.setType(param.getType());
                serviceParam.setPosition(param.getPosition());
                serviceParam.setAlias(param.getAlias());

                Object value = null;
                String position = serviceParam.getPosition();
                String alias = serviceParam.getAlias();
                String routePath = endpointInfo.getRoutePath();
                String path = (String) requestData.get("$path");

                try {
                    value = switch (ParameterPosition.fromValue(position)) {
                        case BODY -> resolveBodyParameter(requestData, alias);
                        case HEADER -> resolveHeaderParameter(requestData, alias);
                        case PATH -> resolvePathParameter(routePath, path, alias);
                        case QUERY -> resolveQueryParameter(requestData, alias);
                        case FORM -> resolveFormParameter(requestData, alias);
                        case FILE -> resolveFileParameter(requestData, alias);
                        default -> resolveAnyParameter(requestData, alias);
                    };
                } catch (Exception e) {
                    logger.error("Failed to resolve parameter: {}", alias, e);
                    serviceParam.setValue(null);
                }
                serviceParam.setValue(value);
                serviceParameters.add(serviceParam);
            }
        }
        return serviceParameters;
    }

    private Object resolveBodyParameter(Map<String, Object> requestData, String paramName) {
        Object body = requestData.get("$body");
        if (body == null) {
            return null;
        }

        if (body instanceof String strBody) {
            try {
                Map jsonMap = mapper.readValue(strBody, Map.class);
                return jsonMap.get(paramName);
            } catch (Exception e) {
                return strBody;
            }
        }
        return body;
    }

    private Object resolveHeaderParameter(Map<String, Object> requestData, String paramName) {
        Map<String, String> headers = (Map<String, String>) requestData.get("$headers");
        return headers != null ? headers.get(paramName) : null;
    }

    private Object resolvePathParameter(String routePath, String actualPath, String paramName) {
        String[] routeParts = routePath.split("/");
        String[] actualParts = actualPath.split("/");

        if (routeParts.length != actualParts.length) {
            return null;
        }

        for (int i = 0; i < routeParts.length; i++) {
            if (routeParts[i].startsWith("{") && routeParts[i].endsWith("}")) {
                String variableName = routeParts[i].substring(1, routeParts[i].length() - 1);
                if (variableName.equals(paramName)) {
                    return actualParts[i];
                }
            }
        }
        return null;
    }

    private Object resolveQueryParameter(Map<String, Object> requestData, String paramName) {
        Map<String, String> queryParams = (Map<String, String>) requestData.get("$queryParams");
        return queryParams != null ? queryParams.get(paramName) : null;
    }

    private Object resolveFormParameter(Map<String, Object> requestData, String paramName) {
        Map<String, String> formParams = (Map<String, String>) requestData.get("$formParams");
        return formParams != null ? formParams.get(paramName) : null;
    }

    private Object resolveFileParameter(Map<String, Object> requestData, String paramName) {
        Map<String, byte[]> fileParams = (Map<String, byte[]>) requestData.get("$fileParams");
        return fileParams != null ? fileParams.get(paramName) : null;
    }

    private Object resolveAnyParameter(Map<String, Object> requestData, String paramName) {
        Object value = resolveQueryParameter(requestData, paramName);
        if (value != null) return value;

        value = resolveFormParameter(requestData, paramName);
        if (value != null) return value;

        value = resolveBodyParameter(requestData, paramName);
        if (value != null) return value;

        return resolveHeaderParameter(requestData, paramName);
    }
}